﻿Imports System.ComponentModel
Imports System.Windows.Forms

''' <summary>
''' Proveedor utilizado para sustituir el tipo de la clase base abstracta por el tipo de la implementación para el modo diseño.
''' </summary>
''' <remarks>Se utiliza como un atributo en la clase base.</remarks>
Friend Class BaseFormProvider
    Inherits TypeDescriptionProvider

#Region " Variables privadas"
    ''' <summary>
    ''' El <see cref="Type"></see> de la clase base abstracta que vamos a manipular.
    ''' </summary>
    ''' <remarks></remarks>
    Private _abstractType As Type
    ''' <summary>
    ''' El <see cref="Type"></see> de la clase concreta que se va a utilizar para el modo diseño.
    ''' </summary>
    ''' <remarks></remarks>
    Private _designType As Type

    ' Constantes
    Private Const C_MISSINGATTRIBUTE As String = "No se ha encontrado el atributo necesario en {0} o en alguno de sus subtipos."
#End Region

#Region " Constructores"

    ''' <summary>
    ''' Genera una nueva instancia de la clase <see cref="BaseFormProvider"></see>.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New()
        MyBase.New(TypeDescriptor.GetProvider(GetType(Form)))
    End Sub
#End Region

#Region " Métodos privados"

    ''' <summary>
    ''' Establece el tipo de la clase base abstracta y su correspondiente tipo de la implementación para el modo diseño.
    ''' </summary>
    ''' <param name="objectType">El <see cref="Type"></see> original.</param>
    ''' <remarks></remarks>
    Private Sub EnsureTypes(ByVal objectType As Type)

        ' Sólo realizamos la operación si el tipo abstracto no se ha cargado.
        If _abstractType Is Nothing Then
            Dim searchType As Type = objectType

            ' Recorremos el tipo que queremos tratar y todos sus 'padres' hasta que encontramos el deseado o llegamos al inicial Object.
            While _abstractType Is Nothing AndAlso searchType IsNot Nothing AndAlso searchType IsNot GetType(Object)
                ' Recorremos todos los atributos personalizados de la clase que sean del tipo requerido.
                For Each classAttribute As BaseFormAttribute In searchType.GetCustomAttributes(GetType(BaseFormAttribute), False)
                    _abstractType = searchType
                    _designType = classAttribute.DesignType
                    Exit For
                Next
                searchType = searchType.BaseType
            End While

            ' Si no hemos obtenido un tipo válido lanzamos una excepción al diseñador.
            If _abstractType Is Nothing Then
                Throw New InvalidOperationException(String.Format(C_MISSINGATTRIBUTE, objectType))
            End If
        End If
    End Sub
#End Region

#Region " Métodos Heredados de TypeDescriptionProvider"

    '''<summary>
    ''' Capturamos la instancia que se intenta generar mediante reflexión para generar una instancia de la implementación de modo diseño en lugar de la clase base abstracta.
    '''</summary>
    '''<param name="provider">Un proveedor de servicio. Opcional.</param>
    '''<param name="objectType">El <see cref="Type"></see> del objeto a generar. No puede ser nothing.</param>
    '''<param name="argTypes">Array opcional de tipos correspondientes al array de valores que se proporcione en <paramref name="args"></paramref>.</param>
    '''<param name="args">Array opcional de valores para utilizar en el constructor del objeto.</param>
    '''<returns>
    ''' El <see cref="T:System.Object"></see> sustituto.
    '''</returns>
    Public Overloads Overrides Function CreateInstance(ByVal provider As IServiceProvider, ByVal objectType As Type, ByVal argTypes As Type(), ByVal args As Object()) As Object
        ' Cargamos los tipos privados
        EnsureTypes(objectType)

        ' Si estamos trabajando con la clase abstracta, cambiamos el tipo por el de la implementación para el modo diseño.
        If objectType Is _abstractType Then
            objectType = _designType
        End If

        Return MyBase.CreateInstance(provider, objectType, argTypes, args)
    End Function

    '''<summary>
    ''' Capturamos la reflexión sobre el objeto e indicamos que el tipo que tiene que utilizarse es la implementación para modo diseño en lugar de la clase base abstracta.
    ''' De esta manera el dieñador de Visual Studio no va a intentar usar la clase base abstracta.
    '''</summary>
    '''<param name="objectType">El tipo original que queremos manipular con la reflexión.</param>
    '''<param name="instance">Una instancia del tipo. Puede ser nothing.</param>
    '''<returns>
    ''' Un <see cref="T:System.Type"></see>.
    '''</returns>
    Public Overloads Overrides Function GetReflectionType(ByVal objectType As Type, ByVal instance As Object) As Type
        ' Cargamos los tipos privados
        EnsureTypes(objectType)

        ' Si estamos trabajando con la clase abstracta, devolvemos la implementación para el modo diseño.
        If objectType Is _abstractType Then
            Return _designType
        End If
        Return MyBase.GetReflectionType(objectType, instance)
    End Function
#End Region

End Class
